#include "RSSRefresher.h"
#include "DocTemplate.h"	
#include "DocStructure.h"
#include "Date.h"

#include "ChannelMGR.h"
#include "ItemMGR.h"
#include "TagMGR.h"

#include <stdio.h>
#include <stdlib.h>
#include <string>

using namespace std;
using namespace userDate;


//Default Function : Create & Destroy & Operator

RSSRefresher::RSSRefresher():
	newDocument(), docStructure(), docTemplate()
{
	this->channelMGR = NULL;
	this->itemMGR = NULL;
	this->tagMGR = NULL;
}

RSSRefresher::RSSRefresher( ChannelMGR* channelMGR, ItemMGR* itemMGR, TagMGR* tagMGR ):
	newDocument(), docTemplate(), docStructure()
{
	this->channelMGR = channelMGR;
	this->itemMGR = itemMGR;
	this->tagMGR = tagMGR;
}
	
	
RSSRefresher::RSSRefresher ( const RSSRefresher& source ) :
	newDocument( source.newDocument ), docTemplate( source.docTemplate)
{
	this->docStructure = source.docStructure;
	this->channelMGR = source.channelMGR;
	this->itemMGR = source.itemMGR;
	this->tagMGR = source.tagMGR;
}

RSSRefresher::~RSSRefresher()
{

}

//General Function
bool RSSRefresher::GetNetFile( RSSDocumentLink* oldDocument )     //Get RSS Document from Web
{
	bool ret = false;
	this->newDocument = "";
	string shellCommand("wget -q -t 1 -T 10 ");								//쉘 커맨드 - wget
//	string shellCommand("wget ");
	int lastIndex = -1;											//주소에서 파일 이름이 시작하는 위치
	lastIndex = (int)( (oldDocument->GetLink()).rfind("/") );

	string originalFile( (oldDocument->GetLink()).substr ( lastIndex + 1, (oldDocument->GetLink()).length() - lastIndex ) );

	shellCommand += "\"";
	shellCommand += (oldDocument->GetLink());
	shellCommand += "\"";		// Shell 명령어를 만든다.

	int shellResult = (int)system ( shellCommand.c_str() );

	if ( shellResult == 0 )
	{
		//File Read
		FILE* file = NULL;
		char buffer[4096] = "";

		file = fopen( originalFile.c_str(), "rt" );

		if ( file != NULL )
		{
			while ( !feof(file) )
			{
				strcpy( buffer, "");
				fgets( buffer, sizeof(buffer), file );

				this->newDocument += buffer;
			}

			fclose( file );

			//그런데, 파일이 만약에 euc-kr이면, utf-8로 다시 변경해야 함.
			int findedVerStart = (int)( this->newDocument.find( "encoding=\"" ) );
			int findedVerEnd   = (int)( this->newDocument.find( "\" ?>" ) );

			cout << ">> Finded Encoding Ver Start:"<< findedVerStart << "	End:" << findedVerEnd << endl;
			if ( findedVerStart > -1 && findedVerEnd > -1 )	//찾았을 경우,
			{
				string verInfo;
				verInfo.assign ( this->newDocument, findedVerStart + 10, findedVerEnd - findedVerStart - 10 );
				cout << ">> Finded Encoding Information: " << verInfo << endl;
				//만약 인코딩 언어가 euc-kr이라면, 
				if ( verInfo == "euc-kr" || verInfo == "EUC-KR" )
				{
					cout << ">> EUC-KR : TRUE " << endl;
					//현재 깨진 상태로 들어가있을 확률이 높은 문서를 삭제한다.
					(this->newDocument).erase();

					string convEncoding = "iconv -c -f euc-kr -t utf-8 ";
					convEncoding += originalFile;
					convEncoding += " > ";
					convEncoding += originalFile;
					convEncoding += "_conv";

					//인코딩 언어를 euc-kr에서 utf-8로 변경한다.
					int encodingRet = (int) system ( convEncoding.c_str() );
//					cout << ">> Encording Convert Result:" << encodingRet << endl;
					
					string convFile( originalFile );
					convFile += "_conv";
					//변경된 파일을 다시 읽어 newDocument에 저장한다.
					file = fopen( convFile.c_str(), "rt" );
					if ( file != NULL )
					{
						while ( !feof(file) )
						{
							strcpy( buffer, "" );
							fgets( buffer, sizeof(buffer), file );

							this->newDocument += buffer;
						}//while !feof(file)
						
						fclose ( file );
						string remove("rm ");
						remove += convFile;
						system( remove.c_str() );

						
					}// file != NULL
				}
			}
		}

		string remove("rm ");
		remove += originalFile;
		system( remove.c_str() );

		ret = true;
	}
	return ret;
}

string RSSRefresher::SetTemplate()              //Set docStructure
{
//	DocTemplate docTemplate;
	string ret;									//Selected RSS Version & Service
	string rssVer;
	string service;
	int rssVersionPtr = -1;						
	int serviceStartPtr = -1;
	int serviceEndPtr = -1;
	int startTagLength = -1;

	//Find RSS Version //
	rssVersionPtr = (int)( this->newDocument.find("<rss version=\"") );
	if ( rssVersionPtr < 0 )
	{
		//if Document type is ATOM, Exception Code input space...	
	}
	
	rssVer = string( this->newDocument.c_str() + rssVersionPtr + 14,
					 this->newDocument.c_str() + rssVersionPtr + 17 );
	
	
	//Find Service Version ///////////////////////////////////////////////
	//Service 업체가 Generator Tag에 있을 경우
	serviceStartPtr = (int)( this->newDocument.find("<generator>") );
	serviceEndPtr = (int)( this->newDocument.find("</generator>") );
	startTagLength = 11;		//<generator>의 길이

	//Service 업체가 Generator Tag에 없을 경우, copyright Tag를 검색한다.
	if ( serviceStartPtr == -1 && serviceEndPtr == -1 )
	{
		serviceStartPtr = (int)( this->newDocument.find("<copyright>") );
		serviceEndPtr = (int)( this->newDocument.find("</copyright>") );			
		startTagLength = 11;	//<copyright>의 길이
	}
	
	//Service 업체의 정보를 잘라온다//
	if ( serviceStartPtr >= 0 && serviceEndPtr >= 0 )
	{
		service = string ( this->newDocument.c_str() + serviceStartPtr + startTagLength,
						   this->newDocument.c_str() + serviceEndPtr );
	}
	else 
	{
		service = "general";
	}
	

	// 검색된 RSS 문서 버전과 서비스 업체명을 가지고 해당 RSS 태그 셋을 불러온다.
	// Tistory RSS 2.0
	if ( strcmp( rssVer.c_str(), "2.0" ) == 0 &&
		 strcmp( service.c_str(), "Tistory 1.1" ) == 0 )
	{
		ret = "tistoryRSS2_0";
	}
	// Naver RSS 2.0
	else if ( strcmp ( rssVer.c_str(), "2.0" ) == 0 &&
			  strcmp ( service.c_str(), "Copyright(c) NHN Corp. All Rights Reserved." ) == 0 )
	{
		ret = "naverRSS2_0";
	}
	// Egloos RSS 2.0
	else if ( strcmp ( rssVer.c_str(), "2.0" ) == 0 &&
			  strcmp ( service.c_str(), "Egloos" ) == 0 )
	{
		ret = "egloosRSS2_0";
	}
	// Allblog RSS 2.0
	else if ( strcmp ( rssVer.c_str(), "2.0" ) == 0 &&
			  strcmp ( service.c_str(), "AllBlog.net RSS FEED Generator / 4.1.2307" ) == 0 )
	{								   //AllBlog.net RSS FEED Generator / 4.1.2307
		ret = "allblogRSS2_0";
	}
	
	// 일반적인 RSS 2.0
	else 
	{
		ret = "generalRSS2_0";
	}
	
	// this->docStructure를 해당 RSS 문서의 종류에 맞는 태그 셋으로 치환한다.
	this->docStructure = this->docTemplate.GetTemplate( ret );

	
	return ret;
}


bool RSSRefresher::CheckUpdate( RSSDocumentLink* oldDocument )    //Compare oldDocument.pubDate & newDocument.pubDate
{
	bool ret = false;
	
	Date oldDate = oldDocument->GetLastRenewalDate();

	string newDateStr;
	int index = (this->docStructure).GetData( string("chPubDate"),&( this->newDocument ),  &newDateStr );

	if ( strcmp( newDateStr.c_str(), "" ) == 0 )
	{
		printf(">> [Update : TRUE ] - this RSS Document(%s) has not Channel PubDate. so, It's need Always Update. \n",
				oldDocument->GetLink().c_str() );
		ret = true;
	}
	else
	{
		Date newDate( const_cast<char*>( newDateStr.c_str() ) );
		
		if ( oldDate < newDate )
		{
			printf(">> [Update : TRUE ] - this RSS Document(%s) is Updated. \n", oldDocument->GetLink().c_str() );
			ret = true;
		}
		else
		{
			printf(">> [Update : FALSE ] - this RSS Document(%s) is not updated. \n", oldDocument->GetLink().c_str() );
		}
	}

	return ret;
}	

bool RSSRefresher::Update( RSSDocumentLink* oldDocument )       //cll channelMgr, itemMgr, tagMgr
{
//1. ChannelLink를 가져온다.
//2. 채널 매니져에서 검색해서 해당 채널이 없을 경우 등록한다.
//3. 태그들을 모두 다 검색해낸다.
//4. 기억장치에 없는 태그들을 추가한다.
//5. 업데이트 날짜가 oldDocumnet의 날짜보다 늦은 아이템을 찾는다. 이때 연결된 태그를 다시 검사한다.
//6. 아이템을 등록한다.

	bool ret = false;

	string document( this->newDocument );
	string temp;
	
	Channel* channelLink = NULL;		//추가한 채널의 리턴받는 장소
	Item* itemLink = NULL;				//추가한 아이템의 리턴받는 장소
	Tag* tagLink = NULL;				//검색된 태그의 리턴받는 장소
	
	unsigned long index = 0;

	index = (this->docStructure).GetData( "chLink", &document, &temp );
	channelLink = (this->channelMGR)->Find( temp );
	
	//채널을 검사하여 등록되지 않은 채널이면 추가함.
	
	string chPubDate;	//Item의 등록 여부를 검사하기 위해 필요함.
	
	if ( channelLink == NULL )
	{
		string chTitle = " ";
		string chLink = " ";
		string chDescription = " ";

		index = (this->docStructure).GetData( "chTitle", &document, &chTitle );
		index = (this->docStructure).GetData( "chLink", &document, &chLink );
		index = (this->docStructure).GetData( "chDesc", &document, &chDescription );
		index = (this->docStructure).GetData( "chPubDate", &document, &chPubDate );

		channelLink = (this->channelMGR)->Record( chLink, chTitle, chDescription, 
												  Date( const_cast<char*>(chPubDate.c_str() )) );
	}
	temp.erase();

	cout << ">> Channel Add Complete." << endl;
	
	//문서에서 모든 태그를 검색해서 새로운 태그가 추가되었는지 검사하여 없을 경우 추가함.
	while ( document.length() > 0 )
	{
//		cout << ">> in the loop for add Tag!!" << endl;

		string* tagSet = NULL;
		int tagSetCount = 0;
		Tag* tag = NULL;;

		index = (this->docStructure).GetTag(&document, &tagSetCount, &tagSet );
		
		for ( int i = 0; i < tagSetCount; i++ )
		{
			tag = (this->tagMGR)->Find( tagSet[i] );

			if ( tag == NULL )
			{
				tag = (this->tagMGR)->Record( tagSet[i] );
//				printf(">> add tag :%s \n", tag->GetTitle().c_str() );
			}
		}
		
		if ( document.length() > index )
		{
//			cout << ">> document.length():" << document.length() << "   index : " << index << endl;
			document = document.assign( document, index + 1, document.length() - index );
			//cout << ">> Cut for Next tag. : " << document << endl; 
		}
		else 
		{
			document.erase();
		}
	}
	document = this->newDocument;	//Reset

	
	
	//OldDocument의 날짜보다 늦게 업데이트된 아이템들을 골라서 업데이트 한다.
	while ( document.length() > 0 )
	{
		//cout << ">> In the loop for add Item." << endl;
		//cout << ">> Document Length : " << document.length() << endl;
		
		string thisItem;
		thisItem.erase();
		string itemTitle;
		itemTitle.erase();
		string itemDescription;
		itemDescription.erase();
		string itemlink;		//위의 스코프 내 itemLink와 헷깔리기 위해 itemLink ->itemlink로 수정
		itemlink.erase();
		string itemPubDate;
		itemPubDate.erase();
		string itemAuthor;
		itemAuthor.erase();
		//ChannelLink는 위에서 찾았음.
	
		index = (this->docStructure).GetData( "item", &document, &thisItem );	//맨 처음 Item을 짤라낸다.
		//cout << "GetData Item" << endl;

//		printf( "this Item Num: %d \n", this->itemMGR->GetCount() );

		index = (this->docStructure).GetData( "itemPubDate", &thisItem, &itemPubDate );
		Date addKeyDate( const_cast<char*>( itemPubDate.c_str() ) );
		//cout << "GetData Item PubDate" << endl;

//		cout << ">> AddKeyDate : " << (char*)(addKeyDate) << endl;

		//임시 올블로그 모든 아이템 받기용 주석 
//		cout << ">> LastRenewalDate : " << (char*)(oldDocument->GetLastRenewalDate() ) << endl << endl;
		Date lastRenewalDate( "Tue, 9 Aug 2006 16:15:02 GMT" );
		
		
		//채널 업데이트 시간보다 늦은 아이템일 경우.
		//if ( addKeyDate > oldDocument->GetLastRenewalDate() )        <----------- 제대로 할 땐 지울것!! 올블로그 전부받기용!!
		if( addKeyDate > lastRenewalDate )
		{
		//	cout << "addDate > lastRenewalDate" << endl;
		//	cout << ">> New Item. " << endl;
		//	cout << "LastRenewalDate : " << (char*)(oldDocument->GetLastRenewalDate() ) << endl;
		//	cout << "Before LastRenewalDate!!" << endl << endl;
		
			index = (this->docStructure).GetData( "itemTitle", &thisItem, &itemTitle ); 
			//cout << "GetItem Title" << endl;
			index = (this->docStructure).GetData( "itemLink", &thisItem, &itemlink ); 
			//cout << "GetItem Link" << endl;
			index = (this->docStructure).GetDesc( itemlink, &thisItem, &itemDescription ); 
			//cout << "GetItem Description" << endl;
			index = (this->docStructure).GetData( "itemAuthor", &thisItem, &itemAuthor ); 
			//cout << "GetItem Author" << endl;
			//검색된 구성요소들로 아이템을 기본 구성하고,  채널을 연결.
			Item buffer( itemTitle, itemDescription, itemlink, Date( const_cast<char*>(itemPubDate.c_str()) ),
						 itemAuthor, channelLink );

			//태그들을 검색하여 아이템에 연결시켜준 뒤.
			while ( thisItem.length() > 0 )
			{
			//	cout << "document.length > 0" << endl;
				Tag* findedTagLink = NULL;
				string* tagStr = NULL;
				int tagStrCount = 0;
				
				index = (this->docStructure).GetTag( &thisItem, &tagStrCount, &tagStr );
				
				for ( int i = 0; i < tagStrCount; i++ )
				{
				//	cout << "TagStrCount > 0 for loop" << endl;
					if ( tagStr[i] != "" )
					{
						//printf("Finded Tag! : %s \n", tagStr->c_str() );

						findedTagLink = (this->tagMGR)->Find( tagStr[i] );
	
						buffer.Record( findedTagLink );
					}
				}
	
				if ( thisItem.length() > index )
				{
//					cout << ">> thisItem.length(): " << thisItem.length() << "   /  index " << index << endl;
					thisItem = thisItem.assign( thisItem, index + 1, thisItem.length() - index );
//					cout << ">> ThisItem : " << thisItem << endl;
				}
				else
				{
					thisItem.erase();
				}
			}
		
			itemLink = (this->itemMGR)->Record( buffer );
			 cout << ">> Complete Add Item [" << buffer.GetTitle() << "]." << endl;
		}

		// document를 잘라서 다음 Item을 검사할 수 있도록 한다.
		index = (this->docStructure).GetData( "item", &document, &thisItem );	

		if ( (document.length() - index) > index )
		{
			document = document.assign( document, index + 1, document.length() - index );
		}
		else 
		{
			document.erase();
		}

		ret = true;
		
	}
	
	//날짜를 갱신시켜줌.
	Date lastRenewalDate;
	lastRenewalDate.Update();
	
	oldDocument->SetLastRenewalDate( lastRenewalDate );

	printf(">> Updated RSS Document : %s \n", oldDocument->GetLink().c_str() );
	//cout << ">> Update Success." << endl;
	 
	return ret;
}


#if 0

int main()
{
	RSSRefresher rssRefresher;
	RSSDocumentLink oldDocument ( "http://uccviewer.tistory.com/rss",
								  "http://uccviewer.tistory.com/",
								  Date ( "Tue, 9 Jan 2007 11:20:47 +0900" ) );

	rssRefresher.GetNetFile( oldDocument );
	rssRefresher.SetTemplate();

	bool flag = rssRefresher.CheckUpdate( oldDocument );
	
	return 0;
}

#endif
